use actix_web::{
    get, post,
    web::{Payload, Query, ServiceConfig},
    Scope,
};
use serde::{Deserialize, Serialize};

use crate::{
    error::{new_err, AppErr},
    server::{
        device::get_conn_by_id,
        web::codec::{new_ok, Cbor, CborRet, RawBuf, Void},
    },
    store::device::{self, Device, OnlineDeviceInfo},
};

#[derive(Debug, Deserialize)]
struct CreateReq {
    name: String,
    mac_addr: String,
    location: String,
    currency_code: String,
    lang: String,
    money: u32,
}

#[derive(Debug, Serialize)]
struct CreateRes {
    id: i64,
}

#[post("/create")]
async fn on_create(req: Cbor<CreateReq>) -> CborRet<CreateRes> {
    let id = device::create(
        &req.name,
        &req.mac_addr,
        &req.location,
        &req.currency_code,
        &req.lang,
        req.money,
    )
    .await?;
    new_ok(CreateRes { id })
}

#[get("/query")]
async fn on_query() -> CborRet<Box<[OnlineDeviceInfo]>> {
    let infos = device::query().await?;
    new_ok(infos)
}

#[derive(Debug, Deserialize)]
struct IdReq {
    id: i64,
}

#[post("/del")]
async fn on_del(id: Cbor<IdReq>) -> Result<Void, AppErr> {
    device::del(id.id).await?;
    Ok(Void)
}

#[post("/edit")]
async fn on_edit(device: Cbor<Device>) -> Result<Void, AppErr> {
    device::edit(&device.0).await?;
    Ok(Void)
}

#[derive(Debug, Deserialize)]
struct RemoteReq {
    id: i64,
    cmd: u8,
}

#[post("/notify")]
async fn on_notify(req: Query<RemoteReq>, payload: Payload) -> Result<Void, AppErr> {
    let buf = payload
        .to_bytes()
        .await
        .map_err(|_| new_err("payload err"))?;
    let conn = get_conn_by_id(req.id)?;

    conn.notify_data(req.cmd, &buf).await?;

    Ok(Void)
}

#[post("/notify_ack")]
async fn on_notify_ack(req: Query<RemoteReq>, payload: Payload) -> Result<Void, AppErr> {
    let buf = payload
        .to_bytes()
        .await
        .map_err(|_| new_err("payload err"))?;
    let conn = get_conn_by_id(req.id)?;

    conn.notify_ack_data(req.cmd, &buf).await?;

    Ok(Void)
}

#[post("req")]
async fn on_req(req: Query<RemoteReq>, payload: Payload) -> Result<RawBuf, AppErr> {
    let buf = payload
        .to_bytes()
        .await
        .map_err(|_| new_err("payload err"))?;
    let conn = get_conn_by_id(req.id)?;

    let frame = conn.req_data(req.cmd, &buf).await?;

    Ok( RawBuf( frame.body()? ) )
}

#[post("ping")]
async fn ping(req: Query<RemoteReq>) -> Result<Void, AppErr> {
    let conn = get_conn_by_id(req.id)?;
    conn.ping().await?;

    Ok(Void)
}

pub fn register(cfg: &mut ServiceConfig) {
    let scope = Scope::new("/device")
        .service(on_create)
        .service(on_query)
        .service(on_del)
        .service(on_edit)
        .service(on_notify)
        .service(on_notify_ack)
        .service(on_req)
        .service(ping);
    cfg.service(scope);
}
